/*
* Copyright 2011 Joshua Carbonneau
*
* This file is part of Texture Chaos.
*
* Texture Chaos is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Texture Chaos is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Texture Chaos.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>

#include "include/simplepng.h"
#include "include/chaos.h"

/**************************************************************
* Create a new SimplePng from the given png file              *
* filename: The png image to read in                          *
* returns: A pointer to the new SimplePng, or null on failure *
***************************************************************/
struct SimplePng* newSimplePng(char* filename)
{
    // Allocate memory and set the type to read
    struct SimplePng* sp = (struct SimplePng*)malloc(sizeof(struct SimplePng));
    sp->type = 0;

    // Copy in the filename
    sp->filename = (char*)malloc(sizeof(char) * (strlen(filename) + 1));
    strcpy(sp->filename, filename);

    // Attempt to open the file
    FILE *infile = fopen(sp->filename, "rb");
    if(!infile)
    {
        debug(1, "Could not open file %s for reading!\n", sp->filename);
        return 0;
    }

    // Read in the first 8 bytes of the file to check if it is a png image
    png_bytep header;
    header = (png_bytep)malloc(sizeof(png_byte) * 8);
    fread(header, 1, 8, infile);
    if(png_sig_cmp(header, 0, 8))
    {
        debug(1, "%s is not a png file!\n", sp->filename);
        free(sp->filename);
        free(header);
        free(sp);
        return 0;
    }
    free(header);

    // Create data structures for the png file
    sp->pngstr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    if(!sp->pngstr)
    {
        debug(1, "Could not create read struct!\n");
        free(sp->filename);
        free(sp);
        return 0;
    }

    sp->pnginf = png_create_info_struct(sp->pngstr);
    if (!sp->pnginf)
    {
        debug(1, "Could not create info struct!\n");
        png_destroy_read_struct(&sp->pngstr, NULL, NULL);
        free(sp->filename);
        free(sp);
        return 0;
    }

    sp->pngend = png_create_info_struct(sp->pngstr);
    if (!sp->pngend)
    {
        debug(1, "Could not create end info struct!\n");
        png_destroy_read_struct(&sp->pngstr, NULL, NULL);
        free(sp->filename);
        free(sp);
        return 0;
    }

    // Set the jmp address in case of libpng errors
    if(setjmp(png_jmpbuf(sp->pngstr)))
    {
        png_destroy_read_struct(&sp->pngstr, NULL, NULL);
        debug(1, "Had a libpng error\n");
        free(sp->filename);
        free(sp);
        return 0;
    }

    // Tell libpng that the header has already been read
    png_set_sig_bytes(sp->pngstr, 8);

    // Set up io with the file and read in the info
    png_init_io(sp->pngstr, infile);
    png_read_info(sp->pngstr, sp->pnginf);

    // Get info about the image
    png_get_IHDR(sp->pngstr, sp->pnginf, &sp->width, &sp->height, &sp->bitdepth,
                 &sp->colortype, &sp->filtermethod, &sp->compressiontype, 
                 &sp->interlacetype);

    // Calculate the number of bytes for each pixel
    sp->pixelSize = png_get_rowbytes(sp->pngstr, sp->pnginf) / sp->width;
    debug(1, "pixelSize: %d\n", sp->pixelSize);

    // Allocate memory for the image data
    sp->rowPointers = (png_bytep*)malloc(sizeof(png_bytep) * sp->height);
    int i;
    for(i=0; i<sp->height; i++)
        sp->rowPointers[i] = 
            (png_byte*)malloc(png_get_rowbytes(sp->pngstr, sp->pnginf));

    // Read in the image data and data from the end of the file
    png_read_image(sp->pngstr, sp->rowPointers);
    png_read_end(sp->pngstr, sp->pngend);

    fclose(infile);

    return sp;
}

/*************************************************
* Deallocate a SimplePng                         *
* sp: Pointer to the SimplePng to be deallocated *
**************************************************/
void destroySimplePng(struct SimplePng* sp)
{
    // Depending on the type of sp, different libpng functions must be 
    // called to deallocate the structures
    if(sp->type == 0)
        png_destroy_read_struct(&sp->pngstr, &sp->pnginf, &sp->pngend);
    else
    {
        png_destroy_info_struct(sp->pngstr, &sp->pngend);
        png_destroy_write_struct(&sp->pngstr, &sp->pnginf);
    }

    // Deallocate rowPointers
    int y;
    for(y=0; y<sp->height; y++)
    {
        free(sp->rowPointers[y]);
    }
    free(sp->rowPointers);

    free(sp->filename);
    free(sp);
}
    
/***********************************************     
* Write the data contained in sp to a png file *
* sp: The SimplePng to write out               *
* filename: The file to write to               *
* returns: 1 on sucess, 0 on failure           *
************************************************/
int writePngFile(struct SimplePng* sp, char* filename)
{
    // Open the file for writing
    FILE* outfile = fopen(filename, "wb");
    if(!outfile)
    {
        debug(1, "Could not open file %s for reading!\n", sp->filename);
        return 0;
    }

    // Set the type of sp to write
    sp->type = 1;

    // Get rid of the old pngstr
    png_destroy_read_struct(&sp->pngstr, 0, 0);
    
    // Set up the new pngstr for writing
    sp->pngstr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    if(!sp->pngstr)
    {
        debug(1, "Could not create read struct!\n");
        fclose(outfile);
        return 0;
    }

    // Set the jmp address in case of libpng errors
    if(setjmp(png_jmpbuf(sp->pngstr)))
    {
        debug(1, "Had a libpng error\n");
        png_destroy_write_struct(&sp->pngstr, NULL);
        fclose(outfile);
        return 0;
    }

    // Set up io with the file and read in the info
    png_init_io(sp->pngstr, outfile);

    // Write out the image data and info
    png_write_info(sp->pngstr, sp->pnginf);
    png_write_image(sp->pngstr, sp->rowPointers);

    // Write the end of the file
    png_write_end(sp->pngstr, sp->pngend);

    fclose(outfile);

    return 1;
}

/********************************************************************
* Colorize the specified rectangle in the image by setting          *
* all color channels but one to zero                                *
* sp: The SimplePng to colorize                                     *
* tly: The y coordinate of the top left corner of the rectangle     *
* tlx: The x coordinate of the top left corner of the rectangle     *
* bry: The y coordinate of the bottom right corner of the rectangle *
* brx: The x coordinate of the bottom right corner of the rectangle *
* color: The color channel to leave on                              *
*********************************************************************/
void colorizeBlock(struct SimplePng* sp, int tly, int tlx, int bry, int brx, 
                   int color)
{
    // Make sure numbers are in range
    color = color % 3;
    tly = tly % sp->height;
    tlx = tlx % sp->width;
    bry = bry % sp->height;
    brx = brx % sp->width;

    int y, x, i;
    // Loop through the rectangle
    for(y=tly; y<bry; y++)
        for(x=tlx; x<brx; x++)
            for(i=0; i<3; i++)
                if(i != color)
                {
                    // If the current byte is not a "color" byte, set it to 0
                    sp->rowPointers[y][(x*sp->pixelSize)+i] = 0;
                }
}

/**************************************************************************
* Switch the contents of two specified rectangles in an image             *
* sp: The SimplePng to be effected                                        *
* tly: The y coordinate of the top left corner of the first rectangle     *
* tlx: The x coordinate of the top left corner of the first rectangle     *
* bry: The y coordinate of the bottom right corner of the first rectangle *
* brx: The x coordinate of the bottom right corner of the first rectangle *
* tlyb: The y coordinate of the top left corner of the second rectangle   *
* tlxb: The x coordinate of the top left corner of the second rectangle   *
***************************************************************************/
void switchBlocks(struct SimplePng* sp, int tly, int tlx, int bry, int brx, 
                  int tlyb, int tlxb)
{
    if(tly > sp->height || tlx > (sp->width * sp->pixelSize) || 
       bry > sp->height || brx > (sp->width * sp->pixelSize))
        return;
    int blockHeight, blockWidth;
    int y, x, i;
    int yb, xb;

    // Calculate the dimentions of the rectangle to be swapped
    blockHeight = bry - tly;
    blockWidth = brx - tlx;
    if(blockHeight < 0 || blockWidth < 0)
        return;

    // Calculate the number of pixels in the rectangle
    int numPixels = blockHeight * blockWidth;

    // Temporary storage for the image data of the rectangle
    png_bytep tmpBlock = (png_bytep)malloc(sizeof(png_byte) * 
        (numPixels * sp->pixelSize));

    // Copy rectangle "a" to tmp
    i = 0;
    for(y=tly; y<bry; y++)
    {
        for(x=tlx*sp->pixelSize; x<(tlx*sp->pixelSize) + 
            (blockWidth*sp->pixelSize); x++)
        {
            tmpBlock[i] = sp->rowPointers[y][x];
            i++;
        }
    }

    // Copy rectangle "b" to "a"
    y = tly; 
    for(yb=tlyb; yb<(tlyb+blockHeight); yb++)
    {
        x = tlx*sp->pixelSize;
        for(xb=tlxb*sp->pixelSize; 
            xb<(tlxb*sp->pixelSize)+(blockWidth*sp->pixelSize); xb++)
        {
            sp->rowPointers[y][x] = sp->rowPointers[yb][xb];
            x++;
        }
        y++;
    }

    // Copy tmp to "b"
    i = 0;
    for(yb=tlyb; yb<(tlyb+blockHeight); yb++)
    {
        for(xb=tlxb*sp->pixelSize; 
            xb<(tlxb*sp->pixelSize)+(blockWidth*sp->pixelSize); xb++)
        {
            sp->rowPointers[yb][xb] = tmpBlock[i];
            i++;
        }
    }
    free(tmpBlock);
}

/********************************************************************
* Shift the RGB values of a specified rectangle one position to     *
* the right                                                         *
* sp: The SimplePng to be effected                                  *
* tly: The y coordinate of the top left corner of the rectangle     *
* tlx: The x coordinate of the top left corner of the rectangle     *
* bry: The y coordinate of the bottom right corner of the rectangle *
* brx: The x coordinate of the bottom right corner of the rectangle *
*********************************************************************/
void shiftColors(struct SimplePng* sp, int tly, int tlx, int bry, int brx)
{
    int y, x;
    int tmp;
    int blockWidth = brx - tlx;

    // Loop through each pixel in the rectangle
    for(y=tly; y<bry; y++)
    {
        // Shift all color values one to the right
        for(x=tlx*sp->pixelSize; 
            x<(tlx*sp->pixelSize)+(blockWidth*sp->pixelSize); x+=sp->pixelSize)
        {
            tmp = sp->rowPointers[y][x+2];
            sp->rowPointers[y][x+2] = sp->rowPointers[y][x+1];
            sp->rowPointers[y][x+1] = sp->rowPointers[y][x];
            sp->rowPointers[y][x] = tmp;
        }
    }
}
            
/********************************************************************
* Randomly set one of the R, G, or B values in a specified          *
* rectangle to a random number                                      *
* sp: The SimplePng to be effected                                  *
* tly: The y coordinate of the top left corner of the rectangle     *
* tlx: The x coordinate of the top left corner of the rectangle     *
* bry: The y coordinate of the bottom right corner of the rectangle *
* brx: The x coordinate of the bottom right corner of the rectangle *
*********************************************************************/
void randomizeColor(struct SimplePng* sp, int tly, int tlx, int bry, int brx)
{
    // Choose R G or B randomly
    int color = random() % 3;

    // Choose the random value
    int value = random() % 256;

    int y, x;
    int blockWidth = brx - tlx;
    // Loop through each pixel in the rectangle
    for(y=tly; y<bry; y++)
    {
        for(x=tlx*sp->pixelSize; 
            x<(tlx*sp->pixelSize)+(blockWidth*sp->pixelSize); x+=sp->pixelSize)
        {
            sp->rowPointers[y][x+color] = value;
        }
    }
}
            
/********************************************************************
* Average the color of all pixels in a specified rectangle and set  *
* all pixels to that color                                          *
* sp: The SimplePng to be effected                                  *
* tly: The y coordinate of the top left corner of the rectangle     *
* tlx: The x coordinate of the top left corner of the rectangle     *
* bry: The y coordinate of the bottom right corner of the rectangle *
* brx: The x coordinate of the bottom right corner of the rectangle *
*********************************************************************/
void averageColor(struct SimplePng* sp, int tly, int tlx, int bry, int brx)
{
    // Array to hold the average of the RGB values
    long avg[3];

    int i;
    int y, x;
    int blockWidth = brx - tlx;
    int numPixels = 0;

    for(i=0; i<3; i++) avg[i] = 0;

    // Loop through each pixel in the rectangle
    for(y=tly; y<bry; y++)
    {
        for(x=tlx*sp->pixelSize; 
            x<(tlx*sp->pixelSize)+(blockWidth*sp->pixelSize); x+=sp->pixelSize)
        {
            // Add the RGB values to the avg array
            if(sp->rowPointers[y][x+3] != 0)
            {
                numPixels++;
                for(i=0; i<3; i++)
                {
                    avg[i] += sp->rowPointers[y][x+i];
                }
            }
        }
    }

    // Divide the RGB values by the number of nontransparant pixels,
    // finding the average color of the rectangle
    if(numPixels > 0)
        for(i=0; i<3; i++)
            avg[i] /= numPixels;

    // Loop through each pixel in the rectangle
    for(y=tly; y<bry; y++)
    {
        for(x=tlx*sp->pixelSize; 
            x<(tlx*sp->pixelSize)+(blockWidth*sp->pixelSize); x+=sp->pixelSize)
        {
            // Set each pixel to the average color
            for(i=0; i<3; i++)
            {
                sp->rowPointers[y][x+i] = avg[i];
            }
        }
    }
}

